using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;

namespace phasor {

	public enum ResizingOption {ScaleFixedA, ScaleFixedB, ScaleBound}

	public class Graph : System.Windows.Forms.UserControl {

		private System.ComponentModel.Container components = null;
		
		#region Vars

		public ArrayList Phasors = new ArrayList();	// ArrayList of n points describing n Phasors

		ResizingOption resizingOption;
		
		// Screen window defined by upper left and lower right offsets (defined to be always postive)
		PointF ulOffSet_S;
		PointF lrOffSet_S;

		// World window defined by y and x max and min
		float xMin, xMax;
		float yMin, yMax;
		
		float xScale, yScale;

		#endregion



		Form1 form1;

		// The Constructor!
		public Graph() {
			// This call is required by the Windows.Forms Form Designer.
			InitializeComponent();

			// Used to stop flickering
			SetStyle(ControlStyles.UserPaint, true);
			SetStyle(ControlStyles.AllPaintingInWmPaint, true);
			SetStyle(ControlStyles.DoubleBuffer, true);

			SetupWorld_1();

			
		}


		public void SetupWorld_1() {
			resizingOption = ResizingOption.ScaleFixedA;
			
			// Set screen window
			ulOffSet_S = new PointF(166f, 4f);
			lrOffSet_S = new PointF(6f, 5f);

			// Set world window
			SetWorldWindow(50f, 50f, new PointF(0.5f, 0f));
		}

		public void SetupWorld_1_Bak() {
			resizingOption = ResizingOption.ScaleFixedA;
			
			// Set screen window
			ulOffSet_S = new PointF(10f, 10f);
			lrOffSet_S = new PointF(10f, 10f);

			// Set world window
			SetWorldWindow(50f, 50f, new PointF(0.5f, 0f));
		}

		public void Test() {
			Console.WriteLine("Test, This is test.");
		} 


		#region Cordinate transformation (World <=> Screen and Rotation)

		public PointF WorldToScreen(PointF p) {
//			// Find change from upper left of world
//			float dx = p.X - xMin;
//			float dy = p.Y - yMax;
//
//			// Adjust for scale
//			float dx_S = dx * xScale;
//			float dy_S = dy * yScale * -1; // -1 indicates change in y axis
//
//			// Add upper left offset
//			float X = dx_S + ulOffSet_S.X;
//			float Y = dy_S + ulOffSet_S.Y;

			return new PointF((p.X - xMin) * xScale + ulOffSet_S.X, (p.Y - yMax) * yScale * -1 + ulOffSet_S.Y);

		}

		public PointF ScreenToWorld(PointF p) {
//			float dx_S = p.X - ulOffSet_S.X;
//			float dy_S = p.Y - ulOffSet_S.Y;
//			
//			float dx = dx_S / xScale;
//			float dy = dy_S / yScale * -1; // -1 indicates change in y axis
//
//			float X = dx + xMin;
//			float Y = dy + yMax;

			return new PointF((p.X - ulOffSet_S.X) / xScale + xMin, (p.Y - ulOffSet_S.Y) / yScale * -1 + yMax);
		}

		
		public PointF WorldChangeToScreenChange(PointF offSet) {
			// Convert world change to screen change
			return new PointF(offSet.X * xScale, offSet.Y * yScale);
		}

		public PointF ScreenChangeToWorldChange(PointF offSet) {
			// Convert screen change to world change
			return new PointF(offSet.X / xScale, offSet.Y / yScale);
		}

		// Rotates point p about point a, through the angle rad passed in through Sin(rad) sinRad and Cos(rad) cosRad
		public PointD Rotate(PointD p, PointD a, double sinRad, double cosRad) {
			// Derived from the combination of 3 transformation matrixies resulting in the matrix:
			// (x - x0)cos(r) - (y - y0)sin(r) + x0
			// (x - x0)sin(r) + (y - y0)cos(r) + y0
			//				  1
			// Where (x0, y0) is the point (x, y) is rotated about by the angle r
			
			double xm0 = p.X - a.X;
			double ym0 = p.Y - a.Y;
			
			return new PointD(xm0*cosRad - ym0*sinRad + a.X, xm0*sinRad + ym0*cosRad + a.Y);
		}


		#endregion
		
		
		
		#region Simple methods to determin Sreen/World hight/width

		private float getScreenWindowHeight() {
			return (float)Height - ulOffSet_S.Y - lrOffSet_S.Y;
		}

		private float getScreenWindowWidth() {
			return (float)Width - ulOffSet_S.X - lrOffSet_S.X;
		}

		private Rectangle getScreenRec() {
			return new Rectangle((int)ulOffSet_S.X, (int)ulOffSet_S.Y, (int)getScreenWindowWidth(), (int)getScreenWindowHeight());
		}

		public float GetWorldHeight() {
			return yMax - yMin;
		}

		public float GetWorldWidth() {
			return xMax - xMin;
		}

		public PointF GetWorldCenter() {
			return new PointF(xMin + (xMax - xMin), yMin + (yMax - yMin));
		}

	
		#endregion
	
		#region World Window transfromations (Set, Move, Zoom)

		public void SetWorldWindowX_FixedAspect(float xMin, float xMax, float yCenter) {
			// Figure what x and y scale will be
			float fuXScale = getScreenWindowWidth() / (xMax - xMin);
			//float fuYScale = getScreenWindowHeight() / (yMax - yMin);

			// Figgure the dx and dy world equivolent of 4 pixls
			float dx = 10f / fuXScale;
			//float dy = 4f / fuYScale;

			//dx = 0.01f;
			xMin -= dx;
			xMax += dx;
			
			float ratio = (this.yMax - this.yMin) / (this.xMax - this.xMin);
			
			float newXRange = xMax - xMin;
			float halphNewYRange = newXRange * ratio / 2f;


	
			SetWorldWindow(xMin, xMax, yCenter - halphNewYRange, yCenter + halphNewYRange);
		}

		public void SetWorldWindowY_FixedAspect(float yMin, float yMax, float xCenter) {
			float fuYScale = getScreenWindowHeight() / (yMax - yMin);
			float dy = 10f / fuYScale;

			yMin -= dy;
			yMax += dy;
			
			float ratio = (this.xMax - this.xMin) / (this.yMax - this.yMin);
			
			float newYRange = yMax - yMin;
			float halphNewXRange = newYRange * ratio / 2f;
	
			SetWorldWindow(xCenter - halphNewXRange, xCenter + halphNewXRange, yMin, yMax);
		}

		public void SetWorldWindow(float xMin, float xMax, float yMin, float yMax) {
			// Set data
			this.xMin = xMin;
			this.xMax = xMax;
			this.yMin = yMin;
			this.yMax = yMax;

			// Devide the screen width/hight representing the world area
			// by the width/hight of the world
			xScale = getScreenWindowWidth() / GetWorldWidth();
			yScale = getScreenWindowHeight() / GetWorldHeight();
		}

		public void SetWorldWindow(float newXScale, float newYScale, PointF cP) {
			if (newXScale <= 50000f && newYScale <= 50000f && 
				newXScale >= 0.50000f && newYScale >= 0.50000f) {

				float halfNewWorldWidth = getScreenWindowWidth() / (newXScale * 2f);
				float halfNewWorldHeight = getScreenWindowHeight() / (newYScale * 2f);

				SetWorldWindow(cP.X - halfNewWorldWidth, cP.X + halfNewWorldWidth, cP.Y - halfNewWorldHeight, cP.Y + halfNewWorldHeight);
			}
		}




		public void ZoomWindow(float factor, PointF cP) {
			SetWorldWindow(xScale * factor, yScale * factor, cP);
		}

		public void ZoomWindow(float factor) {
			// use curent center point
			PointF cP = new PointF(xMin + GetWorldWidth() / 2f, yMin + GetWorldHeight() / 2f);
			SetWorldWindow(xScale * factor, yScale * factor, cP);
		}

		
		public void SetScaleFromLinear(int LinearScale) {
			float scale = (float)(50.0 * Math.Pow(1.95, LinearScale / 100.0));
			
			// TODO: Change this method to include an cP in the signeture (which will be set to (0, E0) by the caller
			// center point
			PointF cP = new PointF(xMin + GetWorldWidth() / 2f, yMin + GetWorldHeight() / 2f);

			SetWorldWindow(scale, scale, cP);
		}

		public int GetLinearScale() {
			return (int)Math.Round( Math.Log( Math.Pow(xScale / 50.0, 1.497388), Math.E) * 100.0, 0);	
		}



		#endregion

		#region Code for reasizing the world window and scale after control is resized

		private void Graph_Resize(object sender, System.EventArgs e) {
			resizeWorldWindow(resizingOption);
		}

		private void resizeWorldWindow(ResizingOption resizingOption) {
			if (resizingOption == ResizingOption.ScaleFixedA) {
				validateWorld_1();

			} else if (resizingOption == ResizingOption.ScaleFixedB) {
				validateWorld_2();
					
			} else if (resizingOption == ResizingOption.ScaleBound) {
				validateWorld_3();
			}

			this.Invalidate();
		}

		
		private void validateWorld_1() {
			// Same as validateWorld_1() but the world window expands in both directions


			// Find difrence between new world Width/height and current world width/height
			float dx = getScreenWindowWidth() / xScale  - GetWorldWidth();
			float dy = getScreenWindowHeight() / yScale - GetWorldHeight();

			// Set new window, expanding x and y from both sides
			SetWorldWindow(xMin - dx/2f, xMax + dx/2f, yMin - dy/2f, yMax + dy/2f);
		
		}
		
		
		private void validateWorld_2() {
			// scale fixed
			// x and y min fixed
			// x and y max grow or contract from right and bottom only

			// Find difrence between new world Width/height and current world width/height
			float dx = getScreenWindowWidth() / xScale  - GetWorldWidth();
			float dy = getScreenWindowHeight() / yScale - GetWorldHeight();

			// Set new window, expanding x and y from right and bottom sides
			SetWorldWindow(xMin, xMax + dx, yMin - dy/2f, yMax);
		}


		private void validateWorld_3() {
			// Scale changes
			SetWorldWindow(xMin, xMax, yMin, yMax);

 		}
 
		
		#endregion


		int lastN;
		double lastPhase;
		double lastR;

		public void UpdatePosInfo(double phase, double R) {
			if (lastN != Phasors.Count - 1) {
				lastN = Phasors.Count - 1;
				infoN = (Phasors.Count - 1).ToString();
			}
			if (lastPhase != phase) {
				lastPhase = phase;
				infoPhase = phase.ToString();
			}
			if (lastR != R) {
				lastR = R;
				infoR = R.ToString();
			}
		}

		public void UpdateRMax(string rMax) {
			infoRMax = rMax;
		}

		public void UpdateRMin(string rMin) {
			infoRMin = rMin;
		}

		#region Drawing



		private void Graph_Paint(object sender, System.Windows.Forms.PaintEventArgs e) {
			if (Phasors != null) {

				drawBoxes(e.Graphics);
				drawInfo(e.Graphics);

				drawAxis(e.Graphics);
				drawData(e.Graphics);
			} else {
				Console.WriteLine("Hey!");
			}

		}


		string infoN;
		string infoPhase;
		string infoR;
		string infoRMax;
		string infoRMin;
		Brush infoBrush = new SolidBrush(Color.Black);
		Font infoFont = new Font("Courier New", 8.25f);

		private void drawInfo(Graphics g) {
			if (form1 !=null) {
				g.DrawString(infoN, infoFont, infoBrush, 87, 27);
				g.DrawString(infoPhase, infoFont, infoBrush, 87, 47);
				g.DrawString(infoR, infoFont, infoBrush, 87, 66);
				g.DrawString(infoRMax, infoFont, infoBrush, 17, 195);
				g.DrawString(infoRMin, infoFont, infoBrush, 17, 213);
			} else {
				g.DrawString("5", infoFont, infoBrush, 87, 27);
				g.DrawString("1.12542", infoFont, infoBrush, 87, 47);
				g.DrawString("200.01256", infoFont, infoBrush, 87, 66);
				g.DrawString("12.0516 @ 1.56675", infoFont, infoBrush, 17, 195);
				g.DrawString("12.0516 @ 1.56675", infoFont, infoBrush, 17, 213);
			}
		}



		private void drawAxis(Graphics g) {
			Pen p = new Pen(Color.Gray);
			
			g.Clip = new Region(getClippingRegion());
			
			PointF x1 = WorldToScreen(new PointF(xMin, 0));
			PointF x2 = WorldToScreen(new PointF(xMax, 0));

			PointF y1 = WorldToScreen(new PointF(0, yMin));
			PointF y2 = WorldToScreen(new PointF(0, yMax));

			try {
				g.DrawLine(p, x1, x2); // x-axis
				g.DrawLine(p, y1, y2); // y-axis

				



			} catch (Exception e) {
				Console.WriteLine("What The?");
			}
		}

		private Rectangle getClippingRegion() {
			return new Rectangle((int)ulOffSet_S.X, (int)ulOffSet_S.Y, 
				this.Width - (int)ulOffSet_S.X - (int)lrOffSet_S.X,
				this.Height - (int)ulOffSet_S.Y - (int)lrOffSet_S.Y);
		}

		private void drawData(Graphics g) {
			if (Phasors.Count == 0) return;
			
			lock (Phasors) {

				g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

				Pen pR = new Pen(Color.Red, 0.02f * (float)Math.Pow(Math.Pow(xScale, 2) + Math.Pow(yScale, 2), 0.5));
				Pen pB = new Pen(Color.Blue, 0.02f * (float)Math.Pow(Math.Pow(xScale, 2) + Math.Pow(yScale, 2), 0.5));
				pR.EndCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor;
				pB.EndCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor;
				//p.DashOffset = 1f;
				//g.DrawLine(p, WorldToScreen(new PointF(0f, 0f)), WorldToScreen((PointF)Phasors[0]));

				Pen p = pB;

				for (int i = 1; i < Phasors.Count; i++) {
					if (p == pR) {
						p = pB;
					} else {
						p = pR;
					}

					
					g.DrawLine(p, WorldToScreen(PointD.ToPointF((PointD)Phasors[i - 1])), WorldToScreen(PointD.ToPointF((PointD)Phasors[i])));
				}

				Pen p2 = new Pen(Color.Pink);
				g.DrawLine(p2, WorldToScreen(PointD.ToPointF((PointD)Phasors[0])), WorldToScreen(PointD.ToPointF((PointD)Phasors[Phasors.Count - 1])));

			}
		}


		Pen grayBoxPen = new Pen(Color.LightGray, 1f);

		private void drawBoxes(Graphics g) {
			g.DrawRectangle(grayBoxPen, 6, 13, 154, 75);
			g.DrawRectangle(grayBoxPen, 6, 99, 154, 71);
			g.DrawRectangle(grayBoxPen, 6, 181, 154, 57);
		}

		#endregion

		#region Generated Code

		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if(components != null)
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		#region Component Designer generated code
		/// <summary> 
		/// Required method for Designer support - do not modify 
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			// 
			// Graph
			// 
			this.BackColor = System.Drawing.Color.White;
			this.Cursor = System.Windows.Forms.Cursors.Arrow;
			this.Name = "Graph";
			this.Size = new System.Drawing.Size(352, 360);
			this.Move += new System.EventHandler(this.Graph_Move);
			this.Resize += new System.EventHandler(this.Graph_Resize);
			this.Load += new System.EventHandler(this.Graph_Load);
			this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.Graph_MouseUp);
			this.Paint += new System.Windows.Forms.PaintEventHandler(this.Graph_Paint);
			this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.Graph_MouseMove);
			this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Graph_MouseDown);

		}
		#endregion


		#endregion

		#region Moving World Window with mouse

		bool leftMouseDown = false;
		PointF leftDown_S;
		float SxMin, SxMax;
		float SyMin, SyMax;

		private void Graph_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e) {
			if (getScreenRec().Contains(e.X, e.Y)) {
				leftMouseDown = true;
				leftDown_S = new PointF((float)e.X, (float)e.Y);
				SxMin = xMin; SxMax = xMax;
				SyMin = yMin; SyMax = yMax;
			}

			downOnN = nRec.Contains(e.X, e.Y);
			downOnPhase = phaseRec.Contains(e.X, e.Y);
			downOnRMax = rMaxRac.Contains(e.X, e.Y);
			downOnRMin = rMinRac.Contains(e.X, e.Y);
		}

		bool downOnN;
		bool downOnPhase;
		bool downOnRMax;
		bool downOnRMin;

		private void Graph_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) {
			leftMouseDown = false;

			if (nRec.Contains(e.X, e.Y) && downOnN) {
				form1.NSlitsButton_Click();
			} else if (phaseRec.Contains(e.X, e.Y) && downOnPhase) {
				form1.PhaseButton_Click();
			} else if (rMaxRac.Contains(e.X, e.Y) && downOnRMax) {
				form1.RMaxButton_Click();
			} else if (rMinRac.Contains(e.X, e.Y) && downOnRMin) {
				form1.RMinButton_Click();
			}

			downOnN = false;
			downOnPhase = false;
			downOnRMax = false;
			downOnRMin = false;
		}

		private void Graph_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) {
			if (e.Button == MouseButtons.Left && leftMouseDown) {
				// Calculate world offset
				PointF offSet = ScreenChangeToWorldChange(new PointF(((float)leftDown_S.X - e.X), ((float)e.Y - leftDown_S.Y)));

				SetWorldWindow(SxMin + offSet.X, SxMax + offSet.X, SyMin + offSet.Y, SyMax + offSet.Y);
			}

			if (nRec.Contains(e.X, e.Y) || phaseRec.Contains(e.X, e.Y) ||
				rMaxRac.Contains(e.X, e.Y) || rMinRac.Contains(e.X, e.Y)) {
				this.Cursor = System.Windows.Forms.Cursors.Hand;
			} else {
				this.Cursor = System.Windows.Forms.Cursors.Default;
			}

		}

		#endregion

		private void Graph_Load(object sender, System.EventArgs e) {
			//Microsoft.Win32.
			if (this.Parent != null && this.Parent is Form1) {
				form1 = (Form1)this.Parent;
			}
		}


		Rectangle nRec = new Rectangle(87, 27, 66, 15);
		Rectangle phaseRec = new Rectangle(87, 47, 66, 15);
		Rectangle rMaxRac = new Rectangle(17, 195, 133, 15);
		Rectangle rMinRac = new Rectangle(17, 213, 133, 15);

		private void Graph_Move(object sender, System.EventArgs e) {

		}
	}
}
